/*******************************************************************************
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.maven.ide.eclipse.wtp.ext;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.MavenExecutionPlan;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.wst.common.componentcore.ComponentCore;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualResource;
import org.maven.ide.eclipse.MavenPlugin;
import org.maven.ide.eclipse.core.IMavenConstants;
import org.maven.ide.eclipse.embedder.IMaven;
import org.maven.ide.eclipse.project.IMavenProjectFacade;
import org.maven.ide.eclipse.project.MavenProjectManager;

/**
 * WebProjectConfiguratorDelegate
 * 
 * @author jer (printstacktrace.org)
 */
class WebProjectConfiguratorDelegate extends
		AbstractProjectConfiguratorDelegate implements
		IProjectConfiguratorDelegate {

	// TODO use ${project.build.directory}/linked
	private static final String LINKED_ROJECT_FOLDER = "target/linked";

	protected void configure(IProject project, MavenProject mavenProject,
			IProgressMonitor monitor) throws CoreException {
	}

	public void setModuleDependencies(IProject project,
			MavenProject mavenProject, IProgressMonitor monitor)
			throws CoreException {
		WarPluginConfiguration warPluginConfiguration = new WarPluginConfiguration(mavenProject,project);
		executeWarExploded(project,mavenProject,warPluginConfiguration,monitor);
		linkWithWarProjectDependencies(warPluginConfiguration,project,mavenProject,monitor);
		updateWtpComponent(warPluginConfiguration,project,mavenProject,monitor);
	}

	protected List<IMavenProjectFacade> getWorkspaceDependencies(
			IProject project, MavenProject mavenProject) {
		Set<IProject> projects = new HashSet<IProject>();
		List<IMavenProjectFacade> dependencies = new ArrayList<IMavenProjectFacade>();
		
		Set<Artifact> artifacts = mavenProject.getArtifacts();
		for (Artifact artifact : artifacts) {
			IMavenProjectFacade dependency = projectManager.getMavenProject(
					artifact.getGroupId(), artifact.getArtifactId(), artifact
							.getVersion());
			if (Artifact.SCOPE_COMPILE.equals(artifact.getScope())
					&& dependency != null
					&& !dependency.getProject().equals(project)
					&& dependency.getFullPath(artifact.getFile()) != null
					&& projects.add(dependency.getProject())) {
				dependencies.add(dependency);
			}
		}
		return dependencies;
	}

	protected void  updateWtpComponent(WarPluginConfiguration warPluginConfiguration,IProject project, MavenProject mavenProject, IProgressMonitor monitor) throws CoreException {
		IVirtualComponent component = ComponentCore.createComponent(project);

	    //Adding WarOverlay folder directory (TODO make more configurable : use ${project.build.finalName} )
	    IFolder overlayFolder = project.getFolder("target").getFolder(mavenProject.getArtifactId());
	    if(overlayFolder.exists())
	    	component.getRootFolder().createLink(overlayFolder.getProjectRelativePath(),IVirtualResource.NONE, monitor);

	    //Adding linked projects
	    IFolder linkedFolder = project.getFolder(LINKED_ROJECT_FOLDER);
		if (linkedFolder.exists()) {
			IResource[] folders = linkedFolder.members();
			for (int i = 0; i < folders.length; i++) {
				if(folders[i].getType() == IResource.FOLDER) {
					component.getRootFolder().createLink(folders[i].getProjectRelativePath(),IVirtualResource.NONE, monitor);
				}
			}
		}
	}
	
	protected void  linkWithWarProjectDependencies(WarPluginConfiguration warPluginConfiguration,IProject project, MavenProject mavenProject, IProgressMonitor monitor) throws CoreException {
		for (IMavenProjectFacade dependency : getWorkspaceDependencies(project, mavenProject)) {
			if(WarPluginConfiguration.isWarProject(dependency.getMavenProject())) {
				WarPluginConfiguration depWarPluginConfig= new WarPluginConfiguration(dependency.getMavenProject(),dependency.getProject());
				// Add filtering...
				String sourceDirectory = depWarPluginConfig.getWarSourceDirectory();
				IFolder srcFolder = dependency.getProject().getFolder(sourceDirectory);
				IFolder desFolder = project.getFolder(LINKED_ROJECT_FOLDER + "/" + dependency.getMavenProject().getArtifactId());
				if (!desFolder.exists() || !desFolder.isLinked()) {
					createFolder( (IFolder) desFolder.getParent(), monitor);
					if (desFolder.exists() && !desFolder.isLinked()) {
						desFolder.delete(true, monitor);
					}					
					desFolder.createLink(srcFolder.getLocation(),IResource.REPLACE, monitor);
					//Refresh the linked directory
					desFolder.setDerived(true);
					desFolder.refreshLocal( IResource.DEPTH_INFINITE, monitor);
				}
			}			
		}
	}

	protected void overlayWarArchiveDependencies(IProject project,
			MavenProject mavenProject, IProgressMonitor monitor)
			throws CoreException {
		WarPluginConfiguration warPluginConfiguration = new WarPluginConfiguration(mavenProject,project);
		executeWarExploded(project,mavenProject,warPluginConfiguration,monitor);
	}

	protected boolean createFolder(IFolder folder, IProgressMonitor monitor)
			throws CoreException {
		if (folder.exists())
			return false;
		if (!folder.getParent().exists())
			createFolder((IFolder) folder.getParent(), monitor);
		folder.create(true, true, monitor);
		folder.setDerived(true);
		return true;
	}

	/**
	   * Executes war:exploded goal to unpack dependent wars.
	   * @throws CoreException 
	   */
	  public void executeWarExploded(IProject project, MavenProject mavenProject, WarPluginConfiguration plugin, IProgressMonitor monitor) throws CoreException {
	      MavenProjectManager projectManager = MavenPlugin.getDefault().getMavenProjectManager();
	      IMavenProjectFacade mavenFacade =  projectManager.getProject(project);
	      MavenExecutionPlan executionPlan = mavenFacade.getExecutionPlan(monitor);
	      MojoExecution explodedConfigMojo = getExecution(executionPlan, "maven-war-plugin", "war");
	      if (explodedConfigMojo == null) {
	        //TODO Better error management
	        return;
	      }
	      
	      //Let's force the generated config files location
	      Xpp3Dom configuration = explodedConfigMojo.getConfiguration();
	      if (configuration == null) {
	        configuration = new Xpp3Dom("configuration");
	        explodedConfigMojo.setConfiguration(configuration);
	      }

	      // Configure the exploded directory (TODO make more configurable : use ${project.build.finalName} ).
	      IFolder explodedFolder = project.getFolder("target/" + mavenProject.getArtifactId());
	      createFolder(explodedFolder, monitor);
	      String explodedLocation = explodedFolder.getRawLocation().toOSString();
	      Xpp3Dom genWebappDirectoryDom = configuration.getChild("webappDirectory");
	      if (genWebappDirectoryDom == null) {
	    	  genWebappDirectoryDom = new Xpp3Dom("webappDirectory"); 
	    	  configuration.addChild(genWebappDirectoryDom);
	      }
	      genWebappDirectoryDom.setValue(explodedLocation);

	      IFolder workFolder = project.getFolder(plugin.getWorkDirectory());
          // If the unpacked folder exist, do nothing.  
	      // if(workFolder.exists())
	      //  return; 

	      // Configure the working directory (like target/war/work).
	      createFolder(workFolder, monitor);
	      String workLocation = workFolder.getRawLocation().toOSString();
	      Xpp3Dom genWorkDirectoryDom = configuration.getChild("workDirectory");
	      if (genWorkDirectoryDom == null) {
	    	  genWorkDirectoryDom = new Xpp3Dom("workDirectory"); 
	    	  configuration.addChild(genWorkDirectoryDom);
	      }
	      genWorkDirectoryDom.setValue(workLocation);
	     
	      //Create a maven request + session
	      IMaven maven = MavenPlugin.getDefault().getMaven();
	      IFile pomResource = project.getFile(IMavenConstants.POM_FILE_NAME);  
	      
	      //TODO check offline behavior, profiles
	      MavenExecutionRequest request = projectManager.createExecutionRequest(pomResource, mavenFacade.getResolverConfiguration(), monitor);
	      MavenSession session = maven.createSession(request, mavenProject);
	      
	      //Execute mvn war:exploded
	      maven.execute(session, explodedConfigMojo, monitor);
	      
	      //Refresh the result folder directory (TODO make more configurable : use ${project.build.finalName} )
	      IFolder resultFolder = project.getFolder("target/"+mavenProject.getArtifactId()); 
	      if(!resultFolder.exists()) 
	    	  createFolder(resultFolder, monitor);
	      resultFolder.setDerived(true);
	      resultFolder.refreshLocal( IResource.DEPTH_INFINITE, monitor);
	      
	      // Remove WEB-INF/lib : TODO find a better way to do that
	      IFolder libFolder = project.getFolder("target/"+mavenProject.getArtifactId()+"/WEB-INF/lib"); 
	      libFolder.delete(true, monitor);
	  }

	    private MojoExecution getExecution(MavenExecutionPlan executionPlan, String artifactId, String goal) {
	      for(MojoExecution execution : executionPlan.getExecutions()) {
	        if(artifactId.equals(execution.getArtifactId()) && goal.equals(execution.getGoal())) {
	          return execution;
	        }
	      }
	      return null;
	    }
}